package service

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"mall/api/inventory"
	"mall/api/mall"
	"mall/api/pay"
	"mall/internal/conf"
	"mall/internal/constant"
	"mall/internal/model"
	"mall/pkg/ecode"
	"mall/pkg/orm"
	"mall/pkg/util"
	"mall/pkg/wechat"
	"strconv"
	"strings"
	"time"
)

func (s *Service) ScheduleOrderRetry(ctx context.Context, req *mall.CommonRetryRequest) (
	resp *mall.DefaultResp, err error) {
	limit := int(req.Limit)
	if limit == 0 {
		limit = 1000
	}
	var (
		messages  []*model.Schedule
		needAlert bool
		alertKeys []string
	)
	if messages, err = s.dao.FindRetrySchedule(ctx, limit); err != nil {
		return
	}
	if len(messages) != 0 {
		fmt.Println("ScheduleOrderRetry message > 0")
		nsqMessages := make([][]byte, len(messages))
		for i, message := range messages {
			var msg json.RawMessage
			if msg, err = json.Marshal(&mall.MallOrderScheduleMessage{
				Type:    mall.ScheduleType(message.ScheduleType),
				OrderId: message.OrderId,
			}); err != nil {
				return
			}
			fmt.Println("ScheduleOrderRetry msg=>")
			fmt.Printf("%+v", msg)
			fmt.Println("=end")
			nsqMessages[i] = msg
			message.Retry += 1
			message.ExecuteTime = time.Now().Add(time.Minute * time.Duration(message.Retry^2))
			if message.Retry > 10 {
				needAlert = true
				alertKeys = append(alertKeys, fmt.Sprintf(
					"%d-%d", message.ScheduleType, message.OrderId))
			}
			if err = s.dao.AutoUpdate(ctx, message); err != nil {
				return
			}
		}
		if err = s.dao.MultiPublishWithoutTransaction(
			ctx, constant.TopicInnerOrder, nsqMessages); err != nil {
			return
		}
	}
	if needAlert {
		alertKey := "mall/alert/schedule"
		if s.dao.RedisClient.Exists(alertKey).Val() == 0 {
			wechat.AsyncSendMarkDown(ctx, wechat.FormatBugMessage(ctx, conf.Conf.Env, conf.Conf.ServiceName,
				"schedule", fmt.Sprintf("定时任务执行存在超过10次的数据，请及时查看"),
				strings.Join(alertKeys, ",")))
			s.dao.RedisClient.Set(alertKey, 1, time.Hour*1)
		}
	}
	resp = &mall.DefaultResp{Status: true}
	return
}

func (s *Service) closeBizRequest(ctx context.Context, requestId uint64) (err error) {
	if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		request := &model.OrderRequest{}
		now := time.Now()
		if err = s.dao.LockRequestById(ctx, request, requestId); err != nil {
			return
		}
		switch pay.PayOrderStatus(request.PayStatus) {
		case pay.PayOrderStatus_PAY_ORDER_STATUS_INIT, pay.PayOrderStatus_PAY_ORDER_STATUS_FAILED:
			if !request.OrderCreated {
				// 未创建订单，直接关闭发布事务性消息即可
				if err = s.closeBizRequestInTransaction(ctx, request, now); err != nil {
					return
				}
				return
			} else if request.AutoCancelTime.Valid && request.AutoCancelTime.Time.Before(now) {
				// 超过时间未支付成功，关闭商城订单
				if err = s.dao.CloseMallOrdersByRequestID(
					ctx, requestId, uint8(mall.MallOrderStatus_MALL_CLOSED_BEFORE_PAYED)); err != nil {
					return
				}
				if err = s.closeBizRequestInTransaction(ctx, request, now); err != nil {
					return
				}
				return
			}
		case pay.PayOrderStatus_PAY_ORDER_STATUS_PAYING:
			return
		default:
			// 可能由于消息重发导致，直接删除定时任务
			if err = s.dao.DeleteSchedule(ctx,
				uint8(mall.ScheduleType_SCHEDULE_TYPE_CLOSE), requestId); err != nil {
				return
			}
		}
		return
	}); err != nil {
		return
	}
	return
}

func (s *Service) updatePayResultInTransaction(ctx context.Context, payOrderView *pay.PayOrderView,
	request *model.OrderRequest) (processSchedule *model.Schedule, err error) {
	var payRemarkJson json.RawMessage
	request.PayOrderNumber = payOrderView.OrderNumber
	switch payOrderView.Status {
	case pay.PayOrderStatus_PAY_ORDER_STATUS_FAILED, pay.PayOrderStatus_PAY_ORDER_STATUS_CLOSED:
		// 标记支付失败(支付关闭由关闭任务触发，这里不同步关闭状态)
		request.PayStatus = uint8(pay.PayOrderStatus_PAY_ORDER_STATUS_FAILED)
		if err = s.dao.Save(ctx, request); err != nil {
			return
		}
		if err = s.dao.DeleteSchedule(ctx,
			uint8(mall.ScheduleType_SCHEDULE_TYPE_PAYING), request.ID); err != nil {
			return
		}
	case pay.PayOrderStatus_PAY_ORDER_STATUS_SUCCESS:
		if payRemarkJson, err = json.Marshal(&model.PayRemark{
			PayType:          payOrderView.PayType,
			PayTypeName:      payOrderView.PayTypeName,
			OriginAmount:     payOrderView.OriginAmount,
			MerchantDiscount: payOrderView.MerchantDiscount,
			PlatformDiscount: payOrderView.PlatformDiscount,
			UserPayed:        payOrderView.UserPayed,
			Receive:          payOrderView.Receive,
			PayTime:          payOrderView.PayTime,
		}); err != nil {
			err = ecode.WithStack(err)
			return
		}
		request.PayStatus = uint8(payOrderView.Status)
		request.PayRemark = orm.JSON(payRemarkJson)
		if err = s.dao.Save(ctx, request); err != nil {
			return
		}
		// 可能由于消息重发导致，直接删除定时任务
		if err = s.dao.DeleteSchedule(ctx,
			uint8(mall.ScheduleType_SCHEDULE_TYPE_PAYING), request.ID); err != nil {
			return
		}
		if err = s.dao.UpdateOrderStatus(
			ctx, request.ID, uint8(mall.MallOrderStatus_MALL_PAYED)); err != nil {
			return
		}
		processSchedule = &model.Schedule{
			ScheduleType: uint8(mall.ScheduleType_SCHEDULE_TYPE_PROCESS_CREATE),
			OrderId:      request.ID,
			ExecuteTime:  time.Now().Add(time.Second * 30),
		}
		if err = s.dao.Save(ctx, processSchedule); err != nil {
			return
		}
	}
	return
}

func (s *Service) queryPayment(ctx context.Context, requestId uint64) (err error) {
	var processSchedule *model.Schedule
	if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		request := &model.OrderRequest{}
		if err = s.dao.LockRequestById(ctx, request, requestId); err != nil {
			return
		}
		switch pay.PayOrderStatus(request.PayStatus) {
		case pay.PayOrderStatus_PAY_ORDER_STATUS_PAYING:
			var payOrderView *pay.PayOrderView
			if payOrderView, err = s.downstream.QueryPayment(ctx, request.PayOrderNumber); err != nil {
				return
			}
			if processSchedule, err = s.updatePayResultInTransaction(ctx, payOrderView, request); err != nil {
				return
			}
			return
		default:
			// 可能由于消息重发导致，直接删除定时任务
			if err = s.dao.DeleteSchedule(ctx,
				uint8(mall.ScheduleType_SCHEDULE_TYPE_PAYING), requestId); err != nil {
				return
			}
		}
		return
	}); err != nil {
		return
	}
	// 支付成功时直接创建履约单
	if processSchedule != nil {
		if err = s.createProcessOrder(ctx, processSchedule.OrderId); err != nil {
			return
		}
	}
	return
}

func (s *Service) createProcessOrder(ctx context.Context, requestId uint64) (err error) {
	if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		request := &model.OrderRequest{}
		if err = s.dao.LockRequestById(ctx, request, requestId); err != nil {
			return
		}
		if !request.InventoryStatus && pay.PayOrderStatus(request.PayStatus) == pay.PayOrderStatus_PAY_ORDER_STATUS_SUCCESS {
			var (
				orders        []*model.Order
				details       []*model.OrderDetail
				inventoryResp *inventory.BatchCreateOrderReply
			)
			if orders, err = s.dao.FindOrdersByRequestId(ctx, request.ID); err != nil {
				return
			}
			orderIds := make([]uint64, len(orders))
			orderList := make([]*inventory.CreateProcessOrderInfo, len(orders))
			orderNumberMap := make(map[string]*model.Order)
			for i, order := range orders {
				orderIds[i] = order.ID
				orderNumberMap[order.OrderNumber] = order
			}
			if details, err = s.dao.FindOrderDetailByOrderIds(ctx, orderIds); err != nil {
				return
			}
			detailMap := make(map[uint64][]*inventory.CreateProcessOrderDetail)
			for _, detail := range details {
				if _, ok := detailMap[detail.OrderId]; !ok {
					detailMap[detail.OrderId] = make([]*inventory.CreateProcessOrderDetail, 0)
				}
				detailMap[detail.OrderId] = append(detailMap[detail.OrderId], &inventory.CreateProcessOrderDetail{
					StorageKey: fmt.Sprintf("%d-%d", detail.ProdId, detail.SkuId),
					Quantity:   detail.Quantity,
					Price:      detail.Price,
					Origin:     detail.Total,
					Discount:   detail.Discount,
					Trans:      detail.Trans,
					Remark: &inventory.ProcessDetailRemark{
						Name:    detail.ProdName,
						Unit:    detail.Unit,
						SkuName: detail.SkuName,
					},
				})
			}
			for i, order := range orders {
				orderList[i] = &inventory.CreateProcessOrderInfo{
					ExtOrderNumber: order.OrderNumber,
					WarehouseId:    order.WarehouseId,
					ProdTrans:      order.Trans - order.DeliverFee,
					DeliveryFee:    order.DeliverFee,
					DeliveryType:   inventory.DeliverType(order.DeliverType),
					Details:        detailMap[order.ID],
					Remark: &inventory.Remark{
						Memo: order.Remark,
					},
				}
				if order.BookingDate.Valid {
					orderList[i].ExpectDeliverDate = order.BookingDate.Time.Format(util.DateLayout)
				}
			}
			addressInfo := &inventory.AddressInfo{}
			if err = json.Unmarshal(request.AddressInfo, addressInfo); err != nil {
				err = ecode.WithStack(err)
				return
			}
			key := strconv.Itoa(int(request.MemberId))
			if inventoryResp, err = s.downstream.CreateProcessOrders(ctx, &inventory.CreateCombinedProcessOrderRequest{
				FreezeOrderNumber: request.FreezeOrderNumber,
				MemberId:          key,
				AddressInfo:       addressInfo,
				OrderList:         orderList,
				OrderKey:          key,
			}); err != nil {
				return
			}
			for _, inventoryOrder := range inventoryResp.Result {
				var inventoryRemarkJson json.RawMessage
				inventoryRemark := &model.InventoryRemark{}
				order := orderNumberMap[inventoryOrder.ExtOrderNumber]
				if err = json.Unmarshal(order.InventoryRemark, inventoryRemark); err != nil {
					err = ecode.WithStack(err)
					return
				}
				inventoryRemark.OrderNumber = inventoryOrder.OrderNumber
				if inventoryRemarkJson, err = json.Marshal(inventoryRemark); err != nil {
					err = ecode.WithStack(err)
					return
				}
				order.InventoryRemark = orm.JSON(inventoryRemarkJson)
				order.InventoryStatus = uint8(inventoryOrder.Status)
				if err = s.dao.Save(ctx, order); err != nil {
					return
				}
			}
			request.InventoryStatus = true
			if err = s.dao.Save(ctx, request); err != nil {
				return
			}
		}
		// 直接删除定时任务
		if err = s.dao.DeleteSchedule(ctx,
			uint8(mall.ScheduleType_SCHEDULE_TYPE_PROCESS_CREATE), requestId); err != nil {
			return
		}
		return
	}); err != nil {
		return
	}
	return
}

func (s *Service) confirmInTransaction(ctx context.Context, order *model.Order) (err error) {
	switch inventory.ProcessOrderStatus(order.InventoryStatus) {
	case inventory.ProcessOrderStatus_ProcessOrderShipped, inventory.ProcessOrderStatus_ProcessOrderWaitMentioned:
		var config *model.Config
		now := time.Now()
		if config, err = s.dao.GetConfig(ctx, order.SgId); err != nil {
			return
		}
		order.CompleteTime = sql.NullTime{Time: now, Valid: true}
		order.InventoryStatus = uint8(inventory.ProcessOrderStatus_ProcessOrderReceived)
		order.RefundDeadline = sql.NullTime{
			Valid: true, Time: now.AddDate(0, 0, int(config.RefundAllowedDays))}
		if err = s.dao.Save(ctx, order); err != nil {
			return
		}
		var confirmMsg json.RawMessage
		if confirmMsg, err = json.Marshal(&inventory.InventoryOrderNotifyPayload{
			OrderNumber: order.OrderNumber,
			Action:      "received",
			Timestamp:   now.Unix(),
			Source:      inventory.SourceEnum_MALL_ORDER,
		}); err != nil {
			return
		}
		if err = s.dao.PublishUniqueNSQMessage(ctx, constant.TopicInventoryNotify, confirmMsg); err != nil {
			return
		}
		// 删除自动确认收货定时任务
		if err = s.dao.DeleteSchedule(ctx,
			uint8(mall.ScheduleType_SCHEDULE_TYPE_AUTO_CONFIRM), order.ID); err != nil {
			return
		}
	case inventory.ProcessOrderStatus_ProcessOrderReceived:
		// 幂等跳过
	default:
		err = ecode.AppendMessage(constant.ErrForbiddenAction, "订单状态不合法，不允许确认收货")
		return
	}
	return
}

func (s *Service) autoConfirm(ctx context.Context, orderId uint64) (err error) {
	if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		order := &model.Order{}
		if err = s.dao.LockOrderByID(ctx, order, orderId); err != nil {
			return
		}
		switch inventory.ProcessOrderStatus(order.InventoryStatus) {
		case inventory.ProcessOrderStatus_ProcessOrderShipped, inventory.ProcessOrderStatus_ProcessOrderWaitMentioned:
			if order.AutoConfirmTime.Valid {
				if order.AutoConfirmTime.Time.Before(time.Now()) {
					if err = s.confirmInTransaction(ctx, order); err != nil {
						return
					}
				}
			} else {
				panic(fmt.Sprintf("自动签收任务缺少时间：%d", orderId))
			}
		default:
			// 直接删除定时任务
			if err = s.dao.DeleteSchedule(ctx,
				uint8(mall.ScheduleType_SCHEDULE_TYPE_AUTO_CONFIRM), orderId); err != nil {
				return
			}
		}
		return
	}); err != nil {
		return
	}
	return
}

func (s *Service) createRefund(ctx context.Context, applyId uint64) (err error) {
	if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		apply := &model.Refund{}
		if err = s.dao.LockApplyByID(ctx, apply, applyId); err != nil {
			return
		}
		if mall.MallRefundStatus(apply.Status) == mall.MallRefundStatus_MALL_REFUND_FINISHED && pay.PayOrderStatus(
			apply.PayStatus) == pay.PayOrderStatus_REFUND_ORDER_STATUS_INIT {
			// 交易完成 && 退款待提交状态
			var (
				refundResp *pay.RefundOrderView
				order      *model.Order
				request    *model.OrderRequest
			)
			if order, err = s.dao.GetOrderByOrderID(ctx, apply.OrderId); err != nil {
				return
			}
			if request, err = s.dao.GetOrderRequestByID(ctx, order.RequestId); err != nil {
				return
			}
			applyRemark := &model.RefundApplyRemark{}
			reviewRemark := &model.ReviewRemark{}
			if err = json.Unmarshal(apply.Remark, applyRemark); err != nil {
				err = ecode.WithStack(err)
				return
			}
			if err = json.Unmarshal(apply.ReviewRemark, reviewRemark); err != nil {
				err = ecode.WithStack(err)
				return
			}
			if refundResp, err = s.downstream.CreateRefundPaymentOrder(ctx, &pay.CreateRefundOrderRequest{
				OrderNumber:  request.PayOrderNumber,
				EmpId:        reviewRemark.EmpID,
				RefundAmount: apply.Trans,
				BizNo:        apply.ApplyNumber,
				Reason:       applyRemark.Reason,
			}); err != nil {
				return
			}
			if err = s.processRefundInTransaction(ctx, apply, refundResp); err != nil {
				return
			}
		}
		if err = s.dao.DeleteSchedule(
			ctx, uint8(mall.ScheduleType_SCHEDULE_TYPE_REFUND_CREATE), applyId); err != nil {
			return
		}
		return
	}); err != nil {
		return
	}
	return
}
